﻿using System;
using System.Collections.Generic;
using System.Linq.Expressions;

namespace FT.Architecture.Queries
{
    public class Query<T>
    {
        private readonly List<Criterion<T>> criteria;
        private readonly List<Query<T>> subQueries;
        private readonly List<Fetch<T>> fetches;

        public Query() : this(new List<Criterion<T>>())
        {
        }

        public Query(IEnumerable<Criterion<T>> criteria)
        {
            this.criteria = new List<Criterion<T>>(criteria);
            this.subQueries = new List<Query<T>>();
            this.fetches = new List<Fetch<T>>();
            Sort = new SortClause<T>(this);
        }

        public IEnumerable<Fetch<T>> Fetches 
        {
            get
            {
                return fetches.AsReadOnly();
            }
        }

        public IEnumerable<Query<T>> SubQueries
        {
            get
            {
                return subQueries.AsReadOnly();
            }
        }

        public IEnumerable<Criterion<T>> Criteria
        {
            get
            {
                return criteria.AsReadOnly();
            }
        }

        public QueryOperator QueryOperator { get; private set; }

        public SortClause<T> Sort { get; private set; }

        public int? FirstResult { get; private set; }

        public int? MaxResults { get; private set; }

        public Query<T> Where(Expression<Func<T, object>> expression, CriteriaOperator criteriaOperator, object value)
        {
            var criterion = Criterion<T>.Create(expression, value, criteriaOperator);
            criteria.Add(criterion);
            return this;
        }

        public Query<T> Where(Expression<Func<T, object>> expression, CriteriaOperator criteriaOperator)
        {
            var criterion = Criterion<T>.Create(expression, criteriaOperator);
            criteria.Add(criterion);
            return this;
        }

        public Query<T> AddSubQuery(Query<T> subQuery)
        {
            subQueries.Add(subQuery);
            return this;
        }

        public Query<T> Fetch(Expression<Func<T, object>> expression)
        {
            fetches.Add(Fetch<T>.Create(expression));
            return this;
        }

        public Query<T> SetOperator(QueryOperator queryOperator)
        {
            QueryOperator = queryOperator;
            return this;
        }

        public Query<T> Paginate(int firstResult, int maxResult)
        {
            FirstResult = firstResult;
            MaxResults = maxResult;
            return this;
        }

        public Query<T> RemovePagination()
        {
            FirstResult = null;
            MaxResults = null;
            return this;
        }
    }
}
